package cn.breeze.basic.service.impl;

import cn.breeze.basic.constant.BaseConstants;
import cn.breeze.basic.exception.BusinessException;
import cn.breeze.basic.jwt.JwtUtils;
import cn.breeze.basic.jwt.RsaUtils;
import cn.breeze.basic.jwt.UserInfo;
import cn.breeze.basic.service.ILoginService;
import cn.breeze.basic.utils.AjaxResult;
import cn.breeze.basic.utils.HttpClientUtils;
import cn.breeze.basic.utils.MD5Utils;
import cn.breeze.basic.utils.StrUtils;
import cn.breeze.basic.vo.LoginPhoneVo;
import cn.breeze.basic.vo.LoginVo;
import cn.breeze.basic.vo.WechatVo;
import cn.breeze.system.domain.Menu;
import cn.breeze.system.mapper.MenuMapper;
import cn.breeze.system.mapper.PermissionMapper;
import cn.breeze.user.domain.Logininfo;
import cn.breeze.user.domain.User;
import cn.breeze.user.domain.Wxuser;
import cn.breeze.user.mapper.LogininfoMapper;
import cn.breeze.user.mapper.UserMapper;
import cn.breeze.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
// 只读事务
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {

    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private WxuserMapper wxuserMapper;
    @Autowired
    private UserMapper userMapper;
    @Value("jwt/pethome_auth_rsa")
    private String jwtRsaPri;
    @Autowired
    private PermissionMapper permissionMapper;
    @Autowired
    private MenuMapper menuMapper;

    /**
    * @Title: account
    * @Description: 账号登录
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/7 12:59
    * @Parameters: [loginVo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Override
    public Map<String, Object> account(LoginVo loginVo) {
        String password = loginVo.getPassword();
        String username = loginVo.getUsername();
        String type = loginVo.getType();
        // 判断参数非空
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(username) || StringUtils.isEmpty(type)){
            throw new BusinessException("参数不能为空！");
        }
        // 根据username去logininfo表中查看数据是否存在
        Logininfo logininfo = logininfoMapper.loadByUsernameAndType(username, type);
        // 判断数据是否存在，不存在，则抛出异常
        if (logininfo == null){
            throw new BusinessException("账号或者密码错误，请重新输入！");
        }
        // 判断密码是否一致
        String passwordTmp = MD5Utils.encrypByMd5(logininfo.getSalt() + password);
        if (!passwordTmp.equals(logininfo.getPassword())){
            throw new BusinessException("账号或者密码错误，请重新输入！");
        }
        return loginSuccessJwtHandler(logininfo);
        /*// 获取uuid
        String token = UUID.randomUUID().toString();
        // 将token作为keylogininfo作为value存入redis中,并设置30分钟有效期
        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
        // 将logininfo重要参数置空
        logininfo.setSalt("");
        logininfo.setPassword("");
        // 将token和logininfo存入到map集合中
        Map<String,Object> map = new HashMap<>();
        map.put("token",token);
        map.put("logininfo",logininfo);
        return map;*/
    }

    /**
    * @Title: logout
    * @Description: 退出登录
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/8 13:02
    * @Parameters: [token]
    * @Return void
    */
    @Override
    public void logout(String token) {
        if (token == null){
            throw new BusinessException("参数不能为空！");
        }
        redisTemplate.delete(token);
    }

    /**
    * @Title: phone
    * @Description: 手机号登录
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/8 15:49
    * @Parameters: [loginPhoneVo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Override
    public Map<String, Object> phone(LoginPhoneVo loginPhoneVo) {
        String phone = loginPhoneVo.getPhone();
        String smsCodeValue = loginPhoneVo.getSmsCodeValue();
        String type = loginPhoneVo.getType();
        // 判断参数非空
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(smsCodeValue) || StringUtils.isEmpty(type)){
            throw new BusinessException("参数不能为空！");
        }
        // 判断验证码是否正确
        // 拼接验证码
        Object phoneCodeKey = redisTemplate.opsForValue().get(BaseConstants.VerifyCodeConstant.BUSINESS_LOGIN_PREFIX + phone);
        if (phoneCodeKey == null){
            throw new BusinessException("验证码已过期，请重新输入");
        }
        // 将redis获取到的验证码拆分为数组
        String[] phoneCodeKeyArray = phoneCodeKey.toString().split(":");
        if (!phoneCodeKeyArray[0].equalsIgnoreCase(smsCodeValue)){
            throw new BusinessException("验证码错误，请重新输入");
        }
        // 根据phone去logininfo表中查看数据是否存在
        Logininfo logininfo = logininfoMapper.loadByPhoneAndType(phone, type);
        // 判断数据是否存在，不存在，则抛出异常
        if (logininfo == null){
            throw new BusinessException("该手机号未被注册，请先前往注册再登录！");
        }
        // 获取uuid
        String token = UUID.randomUUID().toString();
        // 将token作为keylogininfo作为value存入redis中,并设置30分钟有效期
        redisTemplate.opsForValue().set(token,logininfo.toString(),30, TimeUnit.MINUTES);
        // 将logininfo重要参数置空
        logininfo.setSalt("");
        logininfo.setPassword("");
        // 将token和logininfo存入到map集合中
        Map<String,Object> map = new HashMap<>();
        map.put("token",token);
        map.put("logininfo",logininfo);
        return map;
    }

    /**
    * @Title: wechat
    * @Description: 微信登录
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/9 19:13
    * @Parameters: [wechatVo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Override
    public AjaxResult wechat(WechatVo wechatVo) {
        String code = wechatVo.getCode();
        // 判断参数是否为空
        if (code == null){
            throw new BusinessException("参数不能为空，请重新传递");
        }
        // 2.拼接二步曲请求地址，获取access_token、openid
        String tokenUrl = BaseConstants.WechatConstant.TOKEN_URL
                .replace("APPID", BaseConstants.WechatConstant.APPID)
                .replace("SECRET", BaseConstants.WechatConstant.SECRET)
                .replace("CODE", code);
        // 发起请求获取到json字符串
        String jsonStr = HttpClientUtils.httpGet(tokenUrl);
        // 将json字符串转换为json对象
        JSONObject jsonObj = JSONObject.parseObject(jsonStr);
        // 获取openid
        String openid = jsonObj.getString("openid");
        // 获取ACCESS_TOKEN
        String accessToken = jsonObj.getString("access_token");
        // 根据openid去数据库中查看是否存在
        Wxuser wxuser = wxuserMapper.loadByOpenid(openid);
        if (wxuser != null){ // 微信已绑定用户
            // 根据wxuser的userId去查询logininfo对象
            Logininfo logininfo = logininfoMapper.loadByUserId(wxuser.getUserId());
            // 生成token
            String token = UUID.randomUUID().toString();
            // 将用户信息存入到redis中，以token作为key
            redisTemplate.opsForValue().set(token, logininfo.toString(), 30, TimeUnit.MINUTES);
            // 新建一个map集合用户保存返回的信息
            Map<String, Object> map = new HashMap<>();
            // 将logininfo关键信息置空
            logininfo.setSalt("");
            logininfo.setPassword("");
            // 存入token
            map.put("token", token);
            // 存入wxuser
            map.put("logininfo", logininfo);
            // 直接返回map
            return AjaxResult.me().setResultObj(map);
        }
        // 根据openid和ACCESS_TOKEN获取用户信息
        StringBuffer resultObj = new StringBuffer().append("?openId=")
                .append(openid)
                .append("&accessToken=")
                .append(accessToken);
        return AjaxResult.me().setSuccess(false).setMessage("noBinder").setResultObj(resultObj);
    }

    /**
    * @Title: binder
    * @Description: 微信登录绑定账号
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/10 17:28
    * @Parameters: [wechatVo]
    * @Return java.util.Map<java.lang.String,java.lang.Object>
    */
    @Override
    public Map<String, Object> binder(WechatVo wechatVo) {
        String phone = wechatVo.getPhone();
        String verifyCode = wechatVo.getVerifyCode();
        String accessToken = wechatVo.getAccessToken();
        String openId = wechatVo.getOpenId();
        // 判断参数非空
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(verifyCode) || StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)){
            throw new BusinessException("参数不能为空！");
        }
        // 判断验证码是否正确
        Object verifyCodeValue = redisTemplate.opsForValue().get(BaseConstants.VerifyCodeConstant.BUSINESS_BINDER_PREFIX + phone);
        if (verifyCodeValue == null){
            throw new BusinessException("验证码已过期，请重新获取！");
        }
        if (!verifyCode.equals(verifyCodeValue.toString().split(":")[0])){
            throw new BusinessException("验证码错误，请重新输入！");
        }
        // 根据phone去查询user表看是否存在
        User user = userMapper.loadByPhone(phone);
        Logininfo logininfo = null;
        if (user == null){
            // 初始化user
            user = initUser(phone);
            // 初始化logininfo
            logininfo = User2LoginInfo(user);
            // 保存logininfo
            logininfoMapper.save(logininfo);
            // 设置logininfo的主键id到user的logininfo_id字段中
            user.setLogininfoId(logininfo.getId());
            // 保存user信息
            userMapper.save(user);
            // 根据openId和accessToken获取微信用户个人信息
            String wxuserUrl = BaseConstants.WechatConstant.WXUSER_URL
                    .replace("ACCESS_TOKEN", accessToken)
                    .replace("OPENID", openId);
            String jsonStr = HttpClientUtils.httpGet(wxuserUrl);
            // 将json字符串转换为json对象
            Wxuser wxuser = JSON.parseObject(jsonStr, Wxuser.class);
            // 设置user的主键id到wxuser的user_id
            wxuser.setUserId(user.getId());
            // 保存wxuser
            wxuserMapper.save(wxuser);
        } else {
            // 根据user的主键id获取logininfo信息
            logininfo = logininfoMapper.loadByUserId(user.getId());
        }
        // 获取uuid
        String token = UUID.randomUUID().toString();
        // 将token作为keylogininfo作为value存入redis中,并设置30分钟有效期
        redisTemplate.opsForValue().set(token,logininfo.toString(),30, TimeUnit.MINUTES);
        // 将logininfo重要参数置空
        logininfo.setSalt("");
        logininfo.setPassword("");
        // 将token和logininfo存入到map集合中
        Map<String,Object> map = new HashMap<>();
        map.put("token",token);
        map.put("logininfo",logininfo);
        return map;
    }

    /**
    * @Title: initUser
    * @Description: 初始化user
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/10 17:41
    * @Parameters: [registerVo]
    * @Return cn.breeze.user.domain.User
    */
    public User initUser(String phone){
        User user = new User();
        // 设置用户名
        user.setUsername(phone);
        // 設置手机号
        user.setPhone(phone);
        // 获取盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 根据验证和用户输入的密码进行md5加密
        String passwordMd5 = MD5Utils.encrypByMd5(salt + "123456");
        // 设置密码
        user.setPassword(passwordMd5);
        // 设置盐值
        user.setSalt(salt);
        return user;
    }

    /**
     * @Title: User2LoginInfo
     * @Description: 设置Logininfo对象信息
     * @Author: Mr.D
     * @Version: 1.0
     * @Date:  2022/7/6 12:51
     * @Parameters: [user]
     * @Return cn.breeze.user.domain.Logininfo
     */
    public Logininfo User2LoginInfo(User user){
        Logininfo logininfo = new Logininfo();
        // 将user对象拷贝到logininfo中
        BeanUtils.copyProperties(user,logininfo);
        // 设置type字段默认为1，用户
        logininfo.setType(1);
        return logininfo;
    }

    /**
    * @Title: loginSuccessJwtHandler
    * @Description: 用户登录成功后生成JWT加密串并放入到map对象中
    * @Author: Mr.D
    * @Version: 1.0
    * @Date:  2022/7/14 16:56
    * @Parameters: [logininfo]
    * @Return java.util.HashMap<java.lang.String,java.lang.Object>
    */
    public Map<String, Object> loginSuccessJwtHandler(Logininfo logininfo){
        // 用于保存返回的信息
        Map<String,Object> map = new HashMap<>();
        // 1.创建UserInfo对象，存储logininfo对象，并用于后续赋值
        UserInfo userInfo = new UserInfo();
        userInfo.setLogininfo(logininfo);
        // 2.判断是后台用户登录才需要设置菜单信息、权限信息，类型为0那么就是后台用户登录
        if(logininfo.getType() == 0){
            // 2.1.获取用户权限信息，我们只需要sn权限编码即可
            List<String> permissions = permissionMapper.loadOwnPermissionByLogininId(logininfo.getId());
            // 2.2.获取用户菜单信息
            List<Menu> menus = menuMapper.loadOwnMenuByLogininId(logininfo.getId());
            // 2.3.将菜单信息，权限信息放入到userinfo对象中
            userInfo.setPermissions(permissions);
            userInfo.setMenus(menus);
        }
        // 3.根据密钥文件名称获取到密钥
        PrivateKey privateKey = null;
        try {
            privateKey = RsaUtils.getPrivateKey(this.getClass().getClassLoader().getResource(jwtRsaPri).getFile());
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 4.根据密钥、用户信息、获取JWT加密串
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 1440);
        // 5.按照正常逻辑我们直接返回token给前端即可，但是那样前端需要解密，我们使用简化版本JWT，还是返回用户信息
        // 封装token、用户信息、权限信息、菜单信息给前端
        map.put("token", token);
        map.put("logininfo", logininfo);
        map.put("permissions", userInfo.getPermissions());
        map.put("menus", userInfo.getMenus());
        return map;
    }
}
